﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;

using System.Runtime.CompilerServices;
using Microsoft.CSharp.RuntimeBinder;

using Alicanto.Lexing;

namespace Alicanto.Compilation.Code.Operators.Primary
{
    internal class OpMemberAccess : ICodeGenerator
    {
        public Expression Code { get; private set; }

        public IEnumerable<ParseInstruction> Generate(TokenSequence tokens, Scope scope)
        {
            GetEvaluation evaluation = new GetEvaluation();
            yield return evaluation;

            // Get target
            Expression target = evaluation.Expression;
            if (target.Type == typeof(void))
            {
                target = Expression.Constant(scope.ExecutionContext);
            }

            // Get member
            string member = tokens.Pop(TokenType.SimpleName).Str;

            if (target.Type.GetProperty(member) != null || target.Type.GetField(member) != null)
            {
                this.Code = Expression.PropertyOrField(
                    target,
                    member
                );
            }
            else
            {
                if (tokens.Peek(Token.BracketClose) == Token.OpAssign)
                {
                    // Evaluating left hand expression of an assignment.
                    // Dynamic expressions can't be assigned to.
                    yield return new OmitCode();
                    yield return new GenerateNextTokenWith(new OpMemberAccessAssignment(member));
                    yield break;
                }

                Type returnType = typeof(object);

                // Collect input
                List<Expression> input = new List<Expression>();
                input.Add(target);

                CSharpArgumentInfo[] argumentInfo = new CSharpArgumentInfo[input.Count];                
                CallSiteBinder binder = Microsoft.CSharp.RuntimeBinder.Binder.GetMember(
                    CSharpBinderFlags.ConvertExplicit,
                    member,
                    Settings.CONTEXT,
                    argumentInfo
                );

                this.Code = Expression.Dynamic(
                    binder,
                    typeof(object),
                    input
                );

                if (returnType != typeof(object))
                    this.Code = Expression.Convert(
                        this.Code,
                        returnType
                    );
            }
        }

        class OpMemberAccessAssignment : ICodeGenerator
        {
            public Expression Code { get; private set; }
            private string _member;

            internal OpMemberAccessAssignment(string member)
            {
                _member = member;
            }

            public IEnumerable<ParseInstruction> Generate(TokenSequence tokens, Scope scope)
            {
                GetEvaluation evaluation = new GetEvaluation();
                yield return evaluation;

                ParseExpressionRemainder remainder = new ParseExpressionRemainder();
                yield return remainder;

                // Collect input
                List<Expression> input = new List<Expression>();
                input.Add(evaluation.Expression);
                input.Add(remainder.Remainder);

                CSharpArgumentInfo[] argumentInfo = new CSharpArgumentInfo[input.Count];
                CallSiteBinder binder = Microsoft.CSharp.RuntimeBinder.Binder.SetMember(
                    CSharpBinderFlags.ConvertExplicit,
                    _member,
                    Settings.CONTEXT,
                    argumentInfo
                );

                this.Code = Expression.Dynamic(
                    binder,
                    typeof(object),
                    input
                );
            }
        }
    }
}
